#=
     For the whole units of time Month and Year with familiar positive integer multiples,
         [month, quarter, year] as {1, 3, 12} Months and [year, decade] as {1, 10} Years,
     Each of the YearBased units can be expressed exactly using at least one of the MonthBased units.
     None of the MonthBased units can be expressed exactly using any of the YearBased units.
     These abstract subtype relationships support correctness and simplify the fine-grainedness.
=#

for (TypeNames, GranularAffinity) in (
     ((:Nanoseconds, :Microseconds, :Milliseconds), :NanosecondBasedGrain),
     ((:Seconds, :Minutes, :QuarterHours, :Hours), :SecondBasedGrain),
     ((:Days, :Weeks), :DayBasedGrain),
     ((:Months, :Quarters), :MonthBasedGrain),
     ((:Years, :Decades, :Centuries), :YearBasedGrain),
    )
    for TypeName in TypeNames
        @eval begin
             struct $TypeName <: $GranularAffinity
                 value::Int4Time
             end
             @inline value(x::$TypeName) = x.value
             $TypeName(x::$TypeName) = x
             @inline Base.eltype(::Type{$TypeName}) = Int4Time
        end
    end
end

const Granules = (:Nanoseconds, :Microseconds, :Milliseconds,
                  :Seconds, :Minutes, :QuarterHours, :Hours,
                  :Days, :Weeks,
                  :Months, :Quarters, :Years, :Decades, :Centuries)

for T in Granules
  @eval begin
    function $T(x::String)
        val = parse(Int4Span, x)
        return $T(val)
    end

    Base.zero(::Type{$T}) = $T(0); Base.zero(x::$T) = zero($T)
    Base.one(::Type{$T}) = $T(1);  Base.one(x::$T) = one($T)

    Base.isequal(x::$T, y::$T) = isequal(value(x), value(y))
    Base.isless(x::$T, y::$T) = isless(value(x), value(y))

    Base.:(==)(x::$T, y::$T) = (==)(value(x), value(y))
    Base.:(!=)(x::$T, y::$T) = (!=)(value(x), value(y))
    Base.:(<)(x::$T, y::$T) = (<)(value(x), value(y))
    Base.:(<=)(x::$T, y::$T) = (<=)(value(x), value(y))
    Base.:(>=)(x::$T, y::$T) = (>=)(value(x), value(y))
    Base.:(>)(x::$T, y::$T) = (>)(value(x), value(y))

    Base.signbit(x::$T) = signbit(value(x))
    Base.sign(x::$T) = sign(value(x))
    Base.abs(x::$T) = $T(abs(value(x)))
    Base.copysign(x::$T, y::$T) = $T(copysign(value(x), value(y)))
    Base.flipsign(x::$T, y::$T) = $T(flipsign(value(x), value(y)))

    function Base.:(-)(x::$T)
        return $T(-value(x))
    end
    function Base.:(+)(x::$T, y::$T)
        return $T(value(x) + value(y))
    end
    function Base.:(-)(x::$T, y::$T)
        return $T(value(x) - value(y))
    end
    function Base.:(*)(x::$T, y::I) where I<:Integer
        return $T(value(x) * y)
    end
    function Base.:(*)(x::I, y::$T) where I<:Integer
        return $T(x * value(y))
    end

    function Base.div(x::$T, y::I) where I<:Integer
        return $T(div(value(x), y))
    end
    function Base.fld(x::$T, y::I) where I<:Integer
        return $T(fld(value(x), y))
    end
    function Base.cld(x::$T, y::I) where I<:Integer
        return $T(cld(value(x), y))
    end
    function Base.rem(x::$T, y::I) where I<:Integer
        return $T(rem(value(x), y))
    end
    function Base.mod(x::$T, y::I) where I<:Integer
        return $T(mod(value(x), y))
    end
    function Base.divrem(x::$T, y::I) where I<:Integer
        dv, rm = divrem(value(x), y)
        return $T(dv), $T(rm)
    end
    function Base.fldmod(x::$T, y::I) where I<:Integer
        fd, md = fldmod(value(x), y)
        return $T(fd), $T(md)
    end
  end
end

for T in (:NanosecondBasedGrain, :MonthBasedGrain)
  @eval begin
    Base.isequal(x::T1, y::T2) where T2<:$T where T1<:$T = isequal(promote(x,y)...)
    Base.isless(x::T1, y::T2) where T2<:$T where T1<:$T = isless(promote(x,y)...)
    Base.:(==)(x::T1, y::T2) where T2<:$T where T1<:$T = (==)(promote(x,y)...)
    Base.:(!=)(x::T1, y::T2) where T2<:$T where T1<:$T = (!=)(promote(x,y)...)
    Base.:(<)(x::T1, y::T2) where T2<:$T where T1<:$T = (<)(promote(x,y)...)
    Base.:(<=)(x::T1, y::T2) where T2<:$T where T1<:$T = (<=)(promote(x,y)...)
    Base.:(>=)(x::T1, y::T2) where T2<:$T where T1<:$T = (>=)(promote(x,y)...)
    Base.:(>)(x::T1, y::T2) where T2<:$T where T1<:$T = (>)(promote(x,y)...)
    Base.:(+)(x::T1, y::T2) where T2<:$T where T1<:$T = (+)(promote(x,y)...)
    Base.:(-)(x::T1, y::T2) where T2<:$T where T1<:$T = (-)(promote(x,y)...)
  end
end
